﻿// CCombatManager.cs
// Author: Gant Briseno
#region Using Statements
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Casters.source.Objects;
using Microsoft.Xna.Framework.Graphics;
#endregion

namespace Casters.source.Managers
{
    /// <summary>
    /// Handles and organizes all combat
    /// </summary>
    public class CCombatManager
    {
        // Single instance
        private static CCombatManager instance = null;
        public static CCombatManager GetInstance()
        {
            if (instance == null)
                instance = new CCombatManager();

            return instance;
        }

        // Used for storing actions and activating them
        #region Queue Stuff
        public enum ActionType { Recover = 0, Active, Max };

        /// <summary>
        /// Base of an action to be stored
        /// </summary>
        public class CBaseAction
        {
            // The tick to activate this action
            public int m_TickActivation;

            // The type of action
            public ActionType m_Type;

            /// <summary>
            /// Constructor
            /// </summary>
            /// <param name="tick">The tick to activate this action</param>
            /// <param name="type">The type of action</param>
            public CBaseAction(int tick, ActionType type)
            {
                m_TickActivation = tick;
                m_Type = type;
            }
        }

        /// <summary>
        /// Active version of an action to be stored
        /// </summary>
        public class CActiveAction : CBaseAction
        {
            // Trigger to fire off
            public EventID m_AbilityEvent;

            // Where the ability is aimed
            public Vector2 m_Target;

            // The index within the owners spell list
            public int m_SpellSlot;

            /// <summary>
            /// Constructor
            /// </summary>
            /// <param name="tick">The tick to activate this action</param>
            /// <param name="type">The type of action</param>
            /// <param name="eventID">Trigger to fire off</param>
            /// <param name="target">Where the ability is aimed</param>
            /// <param name="spell">The index within the owners spell list</param>
            public CActiveAction(int tick, ActionType type, EventID eventID, Vector2 target, int spell) : base(tick, type)
            {
                m_AbilityEvent = eventID;
                m_Target = target;
                m_SpellSlot = spell;
            }
        }

        /// <summary>
        /// A queue of abilities for a specific object
        /// </summary>
        public class CAbilityQueue
        {
            public CRenderObject m_Owner;
            public List<CBaseAction> m_Queue;

            /// <summary>
            /// Constructor
            /// </summary>
            /// <param name="owner">The owner this queue belongs to</param>
            public CAbilityQueue(CRenderObject owner)
            {
                m_Owner = owner;
                m_Queue = new List<CBaseAction>();
            }
        }
        #endregion

        // Used for replay
        protected List<CAbilityQueue> m_AllAbilities;
        protected int m_TotalTicks;

        // Variables
        protected List<CAbilityQueue> m_AbilityQueues;
        protected int m_CurTick;
        protected int m_MaxTicks;
        protected float m_Timer;
        protected float m_TickDelay;
        protected bool m_bOn;

        // Accessors
        public int GetMaxTicks() { return m_MaxTicks; }

        /// <summary>
        /// Default Constructor
        /// </summary>
        CCombatManager()
        {
            m_CurTick = 0;
            m_MaxTicks = 3;
            m_Timer = 0f;
            m_TickDelay = 0.5f;
            m_bOn = false;
            m_AbilityQueues = new List<CAbilityQueue>();

            m_AllAbilities = new List<CAbilityQueue>();
            m_TotalTicks = 0;
        }

        /// <summary>
        /// Register events
        /// </summary>
        public void LoadContent()
        {
            CEventSystem.RegisterEventHandler(EventID.AddAction, OnAddAction);
            CEventSystem.RegisterEventHandler(EventID.StartOfCombat, OnStartOfCombat);
            CEventSystem.RegisterEventHandler(EventID.EndOfCombat, OnEndOfCombat);
        }

        /// <summary>
        /// Unregister events
        /// </summary>
        public void UnloadContent()
        {
            m_AbilityQueues.Clear();
            m_AllAbilities.Clear();

            CEventSystem.UnregisterEventHandler(EventID.AddAction, OnAddAction);
            CEventSystem.UnregisterEventHandler(EventID.StartOfCombat, OnStartOfCombat);
            CEventSystem.UnregisterEventHandler(EventID.EndOfCombat, OnEndOfCombat);
        }

        /// <summary>
        /// If activated, updates the tick based on a timer
        /// Fires off ability events based on the queues
        /// </summary>
        /// <param name="dt">Delta Time</param>
        public void Update(float dt)
        {
            if (!m_bOn)
                return;

            m_Timer += dt;
            if (m_Timer >= m_TickDelay)
            {
                m_Timer = 0f;
                ++m_CurTick;
                ++m_TotalTicks;

                foreach (CAbilityQueue queue in m_AbilityQueues)
                {
                    foreach (CBaseAction action in queue.m_Queue)
                    {
                        if (action.m_TickActivation == m_CurTick && action.m_Type == ActionType.Active)
                            CEventSystem.BroadcastEvent(((CActiveAction)action).m_AbilityEvent, ((CActiveAction)action).m_Target, ((CActiveAction)action).m_SpellSlot, queue.m_Owner);
                        else if (action.m_TickActivation > m_CurTick)
                            break;
                    }
                }

                if (m_CurTick == m_MaxTicks)
                    CEventSystem.BroadcastEvent(EventID.EndOfCombat);
            }
        }

        public void ReplayUpdate(float dt)
        {
            if (!m_bOn)
                return;

            m_Timer += dt;
            if (m_Timer >= m_TickDelay)
            {
                m_Timer = 0f;
                ++m_CurTick;

                foreach (CAbilityQueue queue in m_AllAbilities)
                {
                    foreach (CBaseAction action in queue.m_Queue)
                    {
                        if (action.m_TickActivation == m_CurTick && action.m_Type == ActionType.Active)
                            CEventSystem.BroadcastEvent(((CActiveAction)action).m_AbilityEvent, ((CActiveAction)action).m_Target, ((CActiveAction)action).m_SpellSlot, queue.m_Owner);
                        else if (action.m_TickActivation > m_CurTick)
                            break;
                    }
                }

                if (m_CurTick == m_TotalTicks)
                    OnEndOfCombat();
            }
        }

        /// <summary>
        /// Draws debug info
        /// </summary>
        /// <param name="font">The font to use</param>
        public void Draw(SpriteFont font)
        {
            Vector2 camera = CGame.GetInstance().m_Camera.m_vPosition;
            float timer = (float)((int)(m_Timer * 10)) * 0.1f;

            CRenderEngine.DrawString(font, "Tick: " + m_CurTick.ToString() + "/" + m_MaxTicks.ToString(), new Vector2(1051f, 85f) + camera, Color.Black);
            CRenderEngine.DrawString(font, "Tick: " + m_CurTick.ToString() + "/" + m_MaxTicks.ToString(), new Vector2(1050f, 84f) + camera, Color.White);
            CRenderEngine.DrawString(font, "Timer: " + timer.ToString() + "/" + m_TickDelay.ToString(), new Vector2(1051f, 101f) + camera, Color.Black);
            CRenderEngine.DrawString(font, "Timer: " + timer.ToString() + "/" + m_TickDelay.ToString(), new Vector2(1050f, 100f) + camera, Color.Black);
        }

        /// <summary>
        /// AddAction: Adds an action to the combat queue
        /// </summary>
        /// <param name="eventArgs[0]">The CRenderObject that is the owner(Player)</param>
        /// <param name="eventArgs[1]">The tick this action should activate on</param>
        /// <param name="eventArgs[2]">The type of the action(Active or Rest)</param>
        /// <param name="eventArgs[3]">The eventID to fire off when activated</param>
        /// <param name="eventArgs[4]">The target of the spell</param>
        /// <param name="eventArgs[5]">The index of the spell in the player's spell list</param>
        public void OnAddAction(params object[] eventArgs)
        {
            CRenderObject owner = (CRenderObject)eventArgs[0];
            int tick = (int)eventArgs[1];
            ActionType type = (ActionType)eventArgs[2];
            EventID eventID = EventID.Max;
            Vector2 target = Vector2.Zero;
            int spell = -1;

            // Check each of the Queues for an owner match
            foreach (CAbilityQueue aq in m_AbilityQueues)
            {
                // If the owners match
                if (aq.m_Owner == owner)
                {
                    // Add the queued action
                    if (type == ActionType.Active)
                    {
                        if (eventArgs[3] != null)
                            eventID = (EventID)eventArgs[3];
                        if (eventArgs[4] != null)
                            target = (Vector2)eventArgs[4];
                        if (eventArgs[5] != null)
                            spell = (int)eventArgs[5];

                        aq.m_Queue.Add(new CActiveAction(tick, type, eventID, target, spell));

                        foreach (CAbilityQueue item in m_AllAbilities)
                        {
                            if (item.m_Owner == owner)
                            {
                                item.m_Queue.Add(new CActiveAction(tick + m_TotalTicks, type, eventID, target, spell));
                                break;
                            }
                        }
                    }
                    else if (type == ActionType.Recover)
                    {
                        aq.m_Queue.Add(new CBaseAction(tick, type));

                        foreach (CAbilityQueue item in m_AllAbilities)
                        {
                            if (item.m_Owner == owner)
                            {
                                item.m_Queue.Add(new CBaseAction(tick + m_TotalTicks, type));
                                break;
                            }
                        }
                    }

                    // Sort to make sure the actions are sorted
                    // by tick activation time
                    //aq.m_Queue.Sort(CompareActions);

                    // If we found a match we added the action
                    return;
                }
            }
            
            // If we get to this point no queue was available for passed in owner
            // Create new Queue
            CAbilityQueue temp = new CAbilityQueue(owner);

            if (type == ActionType.Active)
            {
                if(eventArgs[3] != null)
                    eventID = (EventID)eventArgs[3];
                if(eventArgs[4] != null)
                    target = (Vector2)eventArgs[4];
                if(eventArgs[5] != null)
                    spell = (int)eventArgs[5];

                temp.m_Queue.Add(new CActiveAction(tick, type, eventID, target, spell));
            }
            else if (type == ActionType.Recover)
            {
                temp.m_Queue.Add(new CBaseAction(tick, type));
            }

            m_AbilityQueues.Add(temp);
            m_AllAbilities.Add(temp);
        }

        /// <summary>
        /// A compare function used to sort the queue
        /// Based on tick activation times
        /// </summary>
        /// <param name="x">Left side object(ex [0])</param>
        /// <param name="y">Right side object(ex [1])</param>
        /// <returns>1 == x>, 0 == =, -1 == y></returns>
        int CompareActions(CBaseAction x, CBaseAction y)
        {
            // 1 == X is greater
            // 0 == Both are equal
            // -1 == Y is greater
            if (x == null)
            {
                if (y == null)
                    return 0;
                else
                    return -1;
            }
            else
            {
                if (y == null)
                    return 1;
                else
                {
                    if (x.m_TickActivation > y.m_TickActivation)
                        return 1;
                    else if (x.m_TickActivation == y.m_TickActivation)
                        return 0;
                    else
                        return -1;
                }
            }
        }

        /// <summary>
        /// StartOfCombat: Combat has started
        /// </summary>
        /// <param name="eventArguments">None</param>
        void OnStartOfCombat(params object[] eventArguments)
        {
            m_bOn = true;
            m_Timer = 0f;
            m_CurTick = 0;
        }

        /// <summary>
        /// EndOfCombat: Combat has finished
        /// </summary>
        /// <param name="eventArguments">None</param>
        void OnEndOfCombat(params object[] eventArguments)
        {
            m_bOn = false;
            m_AbilityQueues.Clear();
        }
    }
}
